package com.yehun.luncene;

import com.yehun.entity.Route;
import com.yehun.enums.StatusEnum;
import com.yehun.utils.EnumUtils;
import com.yehun.utils.ParseUtils;
import com.yehun.utils.StringUtils;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.cn.smart.SmartChineseAnalyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.*;
import org.apache.lucene.index.*;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.*;
import org.apache.lucene.search.highlight.*;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.StringReader;
import java.nio.file.Paths;
import java.util.LinkedList;
import java.util.List;

public class LuceneUtils {

    private final static Logger logger = LoggerFactory.getLogger(LuceneUtils.class);

    private static final String LUCENE_PATH = "/home/yehun/logs/lucene";
    private volatile static IndexWriter indexWriter;
    private volatile static IndexReader indexReader;
    private volatile static Directory directory;

    private void initDirectory() throws IOException {
        if (directory == null) {
            synchronized (this) {
                if (directory == null) {
                    directory = FSDirectory.open(Paths.get(LUCENE_PATH));
                }
            }
        }
    }

    private void initIndexWriter() throws Exception{
        this.initDirectory();
        SmartChineseAnalyzer analyzer = new SmartChineseAnalyzer();
        IndexWriterConfig iwc = new IndexWriterConfig(analyzer);
        indexWriter = new IndexWriter(directory, iwc);
    }

    private void initIndexReader() throws Exception{
        if (indexReader == null) {
            synchronized (this) {
                if (indexReader == null) {
                    this.initDirectory();
                    indexReader = DirectoryReader.open(directory);
                }
            }
        }
    }

    /**
     * 添加数据
     */
    public void add(Route route)throws Exception{
        this.initIndexWriter();
        Document doc=new Document();
        doc.add(new StringField("id",String.valueOf(route.getId()), Field.Store.YES));
        /*
         * yes是会将数据存进索引，如果查询结果中需要将记录显示出来就要存进去，如果查询结果
         * 只是显示标题之类的就可以不用存，而且内容过长不建议存进去
         * 使用TextField类是可以用于查询的。
         */
        doc.add(new TextField("url", route.getUrl(), Field.Store.YES));
        doc.add(new TextField("path", route.getPath(), Field.Store.YES));
        doc.add(new TextField("remark", route.getRemark(), Field.Store.YES));
        doc.add(new StoredField("value", route.getValue()));
        doc.add(new StoredField("status", route.getStatus().getValue()));
        doc.add(new StoredField("createTime", ParseUtils.parseTimeStamp(route.getCreateTime())));
        doc.add(new StoredField("updateTime", ParseUtils.parseTimeStamp(route.getUpdateTime())));
        indexWriter.addDocument(doc);
        indexWriter.close();
    }

    /**
     * 更新博客索引
     */
    public void update(Route route)throws Exception{
        this.initIndexWriter();
        Document doc=new Document();
        doc.add(new TextField("url", route.getUrl(), Field.Store.YES));
        doc.add(new TextField("path", route.getPath(), Field.Store.YES));
        doc.add(new TextField("remark", route.getRemark(), Field.Store.YES));
        doc.add(new StoredField("value", route.getValue()));
        doc.add(new StoredField("status", route.getStatus().getValue()));
        doc.add(new StoredField("createTime", ParseUtils.parseTimeStamp(route.getCreateTime())));
        doc.add(new StoredField("updateTime", ParseUtils.parseTimeStamp(route.getUpdateTime())));
        indexWriter.updateDocument(new Term("id", String.valueOf(route.getId())), doc);
        indexWriter.close();
    }

    /**
     * 删除指定索引
     */
    public void remove(String id)throws Exception{
        this.initIndexWriter();
        indexWriter.deleteDocuments(new Term("id", id));
        indexWriter.forceMergeDeletes(); // 强制删除
        indexWriter.commit();
        indexWriter.close();
    }

    public void clean() throws Exception {
        this.initIndexWriter();
        indexWriter.deleteAll();
        indexWriter.forceMergeDeletes();
        indexWriter.commit();
        indexWriter.close();
    }


    /**
     * 查询用户
     * @param keywords 查询关键字
     */
    public List<Route> search(String keywords)throws Exception{
        this.initDirectory();
        this.initIndexReader();
        IndexSearcher indexSearcher = new IndexSearcher(indexReader);
        Analyzer chineseAnalyzer = new SmartChineseAnalyzer();
        Analyzer englishAnalyzer = new StandardAnalyzer();

        //Analyzer analyzer = null;
        //标准分词器，如果用来处理中文，和ChineseAnalyzer有一样的效果，这也许就是之后的版本弃用ChineseAnalyzer的一个原因
        //analyzer = new StandardAnalyzer();
        //第三方中文分词器，有下面2中构造方法。
        //analyzer = new IKAnalyzer();
        //analyzer = new IKAnalyzer(false);
        //analyzer = new IKAnalyzer(true);
        ////空格分词器，对字符串不做如何处理
        //analyzer = new WhitespaceAnalyzer(Version.LUCENE_43);
        ////简单分词器，一段一段话进行分词
        //analyzer = new SimpleAnalyzer(Version.LUCENE_43);
        ////二分法分词器，这个分词方式是正向退一分词(二分法分词)，同一个字会和它的左边和右边组合成一个次，每个人出现两次，除了首字和末字
        //analyzer = new CJKAnalyzer(Version.LUCENE_43);
        ////关键字分词器，把处理的字符串当作一个整体
        //analyzer = new KeywordAnalyzer();
        ////被忽略的词分词器
        //analyzer = new StopAnalyzer(Version.LUCENE_43);


        Query query0 = new QueryParser("url", englishAnalyzer).parse(keywords);
        Query query1 = new QueryParser("path", englishAnalyzer).parse(keywords);
        Query query2 = new QueryParser("remark", chineseAnalyzer).parse(keywords);

        BooleanQuery.Builder booleanQuery = new BooleanQuery.Builder();
        booleanQuery.add(query0, BooleanClause.Occur.SHOULD);
        booleanQuery.add(query1, BooleanClause.Occur.SHOULD);
        booleanQuery.add(query2, BooleanClause.Occur.SHOULD);
        long start = System.currentTimeMillis();
        TopDocs hits = indexSearcher.search(booleanQuery.build(), 100);
        long end = System.currentTimeMillis();
        logger.info("匹配[{}] ，总共花费[{}]毫秒"+"查询到[{}]个记录", keywords, (end-start), hits.totalHits);

        QueryScorer scorer0 = new QueryScorer(query0);
        QueryScorer scorer1 = new QueryScorer(query1);
        QueryScorer scorer2 = new QueryScorer(query2);
        Highlighter highlighter0 = new Highlighter(new SimpleHTMLFormatter("<b><font color='red'>","</font></b>"), scorer0);
        highlighter0.setTextFragmenter(new SimpleSpanFragmenter(scorer0));
        Highlighter highlighter1 = new Highlighter(new SimpleHTMLFormatter("<b><font color='blue'>","</font></b>"), scorer1);
        highlighter1.setTextFragmenter(new SimpleSpanFragmenter(scorer1));
        Highlighter highlighter2 = new Highlighter(new SimpleHTMLFormatter("<b><font color='yellow'>","</font></b>"), scorer2);
        highlighter2.setTextFragmenter(new SimpleSpanFragmenter(scorer2));

        TokenStream tokenStream;
        String highlightString;
        int statusId;
        StatusEnum statusEnum;
        List<Route> routes=new LinkedList<Route>();
        for(ScoreDoc scoreDoc : hits.scoreDocs){
            Document doc = indexSearcher.doc(scoreDoc.doc);
            Route route=new Route();
            route.setId(Integer.parseInt(doc.get(("id"))));
            route.setUrl(doc.get("url"));
            route.setPath(doc.get("path"));
            route.setRemark(doc.get("remark"));
            route.setValue(ParseUtils.parseLong(doc.get(("value"))));
            route.setCreateTime(ParseUtils.parseDate(ParseUtils.parseLong(doc.get(("createTime")))));
            route.setUpdateTime(ParseUtils.parseDate(ParseUtils.parseLong(doc.get(("updateTime")))));
            statusId = ParseUtils.parseInt(doc.get("status"));
            statusEnum = EnumUtils.getEnum(StatusEnum.class, statusId);
            if (statusEnum != null) {
                route.setStatus(statusEnum);
                route.setStatusText(statusEnum.getText());
            }
            String url = doc.get("url");
            if(!StringUtils.isEmpty(url)){
                tokenStream = englishAnalyzer.tokenStream("url", new StringReader(url));
                highlightString = highlighter0.getBestFragment(tokenStream, url);
                route.setUrl(StringUtils.isEmpty(highlightString) ? url : highlightString);
            }
            String path = doc.get("path");
            if(!StringUtils.isEmpty(path)){
                tokenStream = englishAnalyzer.tokenStream("path", new StringReader(path));
                highlightString = highlighter1.getBestFragment(tokenStream, path);
                logger.info("tokenStream={}", tokenStream);
                logger.info("highlightString={}", highlightString);
                route.setPath(StringUtils.isEmpty(highlightString) ? path : highlightString);
            }
            String remark = doc.get("remark");
            if(!StringUtils.isEmpty(remark)){
                tokenStream = chineseAnalyzer.tokenStream("remark", new StringReader(remark));
                highlightString = highlighter2.getBestFragment(tokenStream, remark);
                route.setRemark(StringUtils.isEmpty(highlightString) ? remark : highlightString);
            }
            routes.add(route);
        }
        return routes;
    }

}
